/******************************************************************************

    Simple Authentication and Authorisation System (SAAS)

    crypto.c

    Copyright (C) 2011  Josh Goes


    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    If you require a mailing address for this software, please send your
    mail to the following address:
        SAAS
        GPO Box 2973
        BRISBANE QLD 4001
        AUSTRALIA

******************************************************************************/

#include "saas-build.h"

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>

#include <openssl/aes.h>
#include <openssl/sha.h>



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// HASH FUNCTIONS
//
void sha512( uint8_t *hash , const uint8_t *data , uint32_t data_len )
{
	SHA512_CTX  hash_ctx;
	SHA512_Init( &hash_ctx );
	SHA512_Update( &hash_ctx , data , data_len );
	SHA512_Final( hash , &hash_ctx );
}

void sha512dual( uint8_t *hash , const uint8_t *data1 , uint32_t data1_len , const uint8_t *data2 , uint32_t data2_len )
{
	SHA512_CTX  hash_ctx;
	SHA512_Init( &hash_ctx );
	SHA512_Update( &hash_ctx , data1 , data1_len );
	SHA512_Update( &hash_ctx , data2 , data2_len );
	SHA512_Final( hash , &hash_ctx );
}

void sha256( uint8_t *hash , const uint8_t *data , uint32_t data_len )
{
	SHA256_CTX  hash_ctx;
	SHA256_Init( &hash_ctx );
	SHA256_Update( &hash_ctx , data , data_len );
	SHA256_Final( hash , &hash_ctx );
}

void sha256dual( uint8_t *hash , const uint8_t *data1 , uint32_t data1_len , const uint8_t *data2 , uint32_t data2_len )
{
	SHA256_CTX  hash_ctx;
	SHA256_Init( &hash_ctx );
	SHA256_Update( &hash_ctx , data1 , data1_len );
	SHA256_Update( &hash_ctx , data2 , data2_len );
	SHA256_Final( hash , &hash_ctx );
}




//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// ENCRYPTION FUNCTIONS
//

//
// AES
//

void _crypto_pad_aes( uint8_t *pkt , uint16_t *pkt_len )
{
	if ( *pkt_len % AES_BLOCK_SIZE != 0 )
	{
		// the packet length does not completely fill up an AES block, so
		// add some random bytes at the end for filler.
		int extra = AES_BLOCK_SIZE - ( *pkt_len % AES_BLOCK_SIZE );
		gen_rand_data( pkt+(*pkt_len) , extra );
		*pkt_len += extra;
	}
}

void _crypto_encrypt_aes256cbc_sha256( saas_t *inst , uint8_t *pkt , uint16_t *pkt_len , const uint8_t *secret , uint32_t secret_len )
{
	// TODO: add support for AES-256-CBC encryption with SHA256 hashing.
}

void _crypto_decrypt_aes256cbc_sha256( saas_t *inst , uint8_t *pkt , uint16_t pkt_len , const uint8_t *secret , uint32_t secret_len )
{
	// TODO: add support for AES-256-CBC encryption with SHA256 hashing.
}

void _crypto_encrypt_aes256ecb_sha256( saas_t *inst , uint8_t *pkt , uint16_t *pkt_len , const uint8_t *secret , uint32_t secret_len )
{
	// first make sure that pktlen is a multiple of AES_BLOCK_SIZE.
	_crypto_pad_aes( pkt , pkt_len );
	uint32_t pl = *pkt_len;

	// hash the secret to form an AES key.
	uint8_t  key[ SHA256_DIGEST_LENGTH ];
	sha256( key , secret , secret_len );

	// set the encryption key
	unsigned char buf[ AES_BLOCK_SIZE ];
	AES_KEY  enckey;
	AES_set_encrypt_key( key , SHA256_DIGEST_LENGTH*8 , &enckey );

	while ( pl > 0 )
	{
		AES_ecb_encrypt( pkt , buf , &enckey , AES_ENCRYPT );
		memcpy( pkt , buf , AES_BLOCK_SIZE );
		pl -= AES_BLOCK_SIZE;
		pkt += AES_BLOCK_SIZE;
	}
}

void _crypto_decrypt_aes256ecb_sha256( saas_t *inst , uint8_t *pkt , uint16_t pkt_len , const uint8_t *secret , uint32_t secret_len )
{
	// first make sure that pktlen is a multiple of AES_BLOCK_SIZE.
	if ( pkt_len % AES_BLOCK_SIZE != 0 )
		return;

	// hash the secret to form an AES key.
	uint8_t  key[ SHA256_DIGEST_LENGTH ];
	sha256( key , secret , secret_len );

	// set the encryption key
	unsigned char buf[AES_BLOCK_SIZE];
	AES_KEY  deckey;
	AES_set_decrypt_key( key , SHA256_DIGEST_LENGTH*8 , &deckey );

	while ( pkt_len > 0 )
	{
		AES_ecb_encrypt( pkt , buf , &deckey , AES_DECRYPT );
		memcpy( pkt , buf , AES_BLOCK_SIZE );
		pkt_len -= AES_BLOCK_SIZE;
		pkt += AES_BLOCK_SIZE;
	}

}

void _crypto_encrypt_aes128cbc_sha256xor( saas_t *inst , uint8_t *pkt , uint16_t *pkt_len , const uint8_t *secret , uint32_t secret_len )
{
}


void _crypto_decrypt_aes128cbc_sha256xor( saas_t *inst , uint8_t *pkt , uint16_t pkt_len , const uint8_t *secret , uint32_t secret_len )
{
}


void _crypto_encrypt_aes128ecb_sha256xor( saas_t *inst , uint8_t *pkt , uint16_t *pkt_len , const uint8_t *secret , uint32_t secret_len )
{
}


void _crypto_decrypt_aes128ecb_sha256xor( saas_t *inst , uint8_t *pkt , uint16_t pkt_len , const uint8_t *secret , uint32_t secret_len )
{
}

uint16_t _conf_parse_enc_type( saas_t *inst , char *enc_type )
{
	inst->enc_type = 0;

	if ( strcmp( enc_type , "aes256cbc-sha256"    ) == 0 ) inst->enc_type = SAAS_ENC_TYPE_AES256CBC_SHA256;
	if ( strcmp( enc_type , "aes256ecb-sha256"    ) == 0 ) inst->enc_type = SAAS_ENC_TYPE_AES256ECB_SHA256;
	if ( strcmp( enc_type , "aes128cbc-sha256xor" ) == 0 ) inst->enc_type = SAAS_ENC_TYPE_AES128CBC_SHA256XOR;
	if ( strcmp( enc_type , "aes128ecb-sha256xor" ) == 0 ) inst->enc_type = SAAS_ENC_TYPE_AES128ECB_SHA256XOR;

	if ( inst->enc_type == 0 )
	{
		// we couldn't parse the encryption type :|
		logm( inst , LOG_DEBUG , "Error parsing conf file. Unknown enc_type given \"%s\"" , enc_type );
		return SAAS_OP_PARSE_ERROR;
	}

	return SAAS_OP_SUCCESS;
}






//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// INTERNAL FUNCTIONS
//

void pkt_encrypt( saas_t *inst , uint8_t *pkt , uint16_t *pkt_len )
{
	// append the current time to the secret.
	uint8_t realsecret[ SAAS_MAX_SECRET_LEN + 14 ];
	time_t rawtime;
	struct tm * ptm;
	uint8_t  datetime_len = 0;
	time ( &rawtime );
	ptm = gmtime ( &rawtime );

	char buf[16];
	memset( buf , 0 , sizeof( buf ) );
	if ( inst->key_time_type == SAAS_KEY_TIME_TYPE_MIN )
	{
		strftime( buf , sizeof( buf ) , "%Y%m%d%H%M" , ptm );
		datetime_len = 12;
	}
	else
	{
		strftime( buf , sizeof( buf ) , "%Y%m%d%H%M%S" , ptm );
		datetime_len = 14;
	}

	memset( realsecret , 0 , sizeof( realsecret ) );
	strncpy( (char *) realsecret , (char *) inst->secret , inst->secret_len );
	strncat( (char *) realsecret , buf , datetime_len );
	uint32_t realsecret_len = inst->secret_len + datetime_len;


	// look up the enc_type parameter and call the right function to do the job.
	switch ( inst->enc_type )
	{

		case SAAS_ENC_TYPE_AES256CBC_SHA256:
			_crypto_encrypt_aes256cbc_sha256( inst , pkt , pkt_len , realsecret , realsecret_len );
			break;

		case SAAS_ENC_TYPE_AES256ECB_SHA256:
			_crypto_encrypt_aes256ecb_sha256( inst , pkt , pkt_len , realsecret , realsecret_len );
			break;

		case SAAS_ENC_TYPE_AES128CBC_SHA256XOR:
			_crypto_encrypt_aes128cbc_sha256xor( inst , pkt , pkt_len , realsecret , realsecret_len );
			break;

		case SAAS_ENC_TYPE_AES128ECB_SHA256XOR:
			_crypto_encrypt_aes128ecb_sha256xor( inst , pkt , pkt_len , realsecret , realsecret_len );
			break;

		default:
			break;

	}

}



uint16_t pkt_decrypt( saas_t *inst , uint8_t *pkt , uint16_t pkt_len )
{
	// SHA256 hash output, input, and AES keys...
	uint8_t  realsecret[SAAS_MAX_SECRET_LEN+14];

	// temporarily variables used for the time calc.
	time_t rawtime;
	struct tm * ptm;
	char buf[16];
	uint32_t realsecret_len;


	// a place to decrypt to without overwriting the original packet
	uint8_t *dec = malloc( pkt_len );
	memcpy( dec , pkt , pkt_len );

	// temporary variables used to verify the output
	uint32_t *magic_id;

	int k;

	for ( k = -1 ; k < 3 ; k++ )
	{
		// forge the secret with the current_time + k
		time ( &rawtime );
		rawtime += k;
		ptm = gmtime ( &rawtime );

		memset( buf , 0 , sizeof( buf ) );
		strftime( buf , sizeof( buf ) , "%Y%m%d%H%M%S" , ptm );

		memset( realsecret , 0 , sizeof( realsecret ) );
		strncpy( (char *) realsecret , (char *) inst->secret , inst->secret_len );
		strncat( (char *) realsecret , buf , 14 );
		realsecret_len = inst->secret_len + 14;

		// then we'll attempt a decryption and then analyse the output to see
		// if it was successful.
		switch ( inst->enc_type )
		{

			case SAAS_ENC_TYPE_AES256CBC_SHA256:
				_crypto_decrypt_aes256cbc_sha256( inst , dec , pkt_len , realsecret , realsecret_len );
				break;

			case SAAS_ENC_TYPE_AES256ECB_SHA256:
				_crypto_decrypt_aes256ecb_sha256( inst , dec , pkt_len , realsecret , realsecret_len );
				break;

			case SAAS_ENC_TYPE_AES128CBC_SHA256XOR:
				_crypto_decrypt_aes128cbc_sha256xor( inst , dec , pkt_len , realsecret , realsecret_len );
				break;

			case SAAS_ENC_TYPE_AES128ECB_SHA256XOR:
				_crypto_decrypt_aes128ecb_sha256xor( inst , dec , pkt_len , realsecret , realsecret_len );
				break;

		}

		// analyse the output and look for a valid magic ID.
		magic_id = (uint32_t *) dec + 8;
		*magic_id = u32fromnet( *magic_id );
		if ( *magic_id == SAAS_MAGIC_RESP )
		{
			// we got it!
			memcpy( pkt , dec , pkt_len );
			free( dec );
			return SAAS_OP_SUCCESS;
		}

		// so the decryption failed. reset the dec variable and try again.
		memcpy( dec , pkt , pkt_len );
	}

	free( dec );
	return SAAS_OP_DECRYPT_ERROR;
}


